package QC.MessageBox.service;

import QC.MessageBox.beans.*;
import QC.MessageBox.beans.wbmsg.WBTextMsg;
import QC.MessageBox.beans.wxmsg.media.MPNews;
import QC.MessageBox.beans.wxmsg.normalmsg.WXGroupSendByList;
import QC.MessageBox.beans.wxmsg.response.FansList;
import QC.MessageBox.beans.wxmsg.response.WBFansList;
import QC.MessageBox.common.Constants;
import QC.MessageBox.common.PeriodicTime;
import QC.MessageBox.common.RCUtils;
import QC.MessageBox.common.WxApiErrorResult;
import QC.MessageBox.exceptions.RequestAppBackException;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Created by Andaren on 2017/5/22.
 *      [后期添加请求超时，超时重发3次]
 * Used for:
 */
public class WebClientService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebClientService.class);

    // 微信请求最大失败重试次数
    private static final Integer WX_API_REQUEST_MAX_FAULT_TIME = 3;

    private WebClient appClient;
    private WebClient rcClient;
    private WebClient wxClient;
    private WebClient wbClient;
    private WebClient wbmClient;
    private WebClient commClient;
    public WebClientService(Vertx vertx) {
        appClient = WebClient.create(vertx, new WebClientOptions()
                .setSsl(false)
                .setConnectTimeout(5000)
                .setKeepAlive(false)
                .setTcpKeepAlive(false)
                .setDefaultHost(getAPPBackgroundHost())
                .setDefaultPort(getAPPBackgroundPort()));
        rcClient = WebClient.create(vertx, new WebClientOptions()
                .setSsl(false)
                .setConnectTimeout(5000)
                .setKeepAlive(true)// 融云请求timeout
                .setTcpKeepAlive(true)
                .setDefaultHost(Constants.RC_INTERFACE_HOST)
                .setDefaultPort(Constants.RC_INTERFACE_PORT));
        wxClient = WebClient.create(vertx, new WebClientOptions()
//                .setTrustStoreOptions(new JksOptions()
//                        .setPath("bill.keystore")
//                        .setPassword("billbill")
//                )
//                .setSsl(true)
                .setConnectTimeout(5000)
                .setDefaultHost(Constants.WX_INTERFACE_HOST)
                .setDefaultPort(Constants.WX_INTERFACE_SSLPORT));
        wbClient = WebClient.create(vertx, new WebClientOptions()
                .setSsl(true)
                .setConnectTimeout(5000)
                .setDefaultHost(Constants.WB_INTERFACE_HOST)
                .setDefaultPort(Constants.WB_INTERFACE_SSLPORT));
        wbmClient = WebClient.create(vertx, new WebClientOptions()
                .setSsl(true)
                .setConnectTimeout(5000)
                .setDefaultHost(Constants.WB_INTERFACE_HOST_M)
                .setDefaultPort(Constants.WB_INTERFACE_SSLPORT));
        commClient = WebClient.create(vertx, new WebClientOptions()
                .setConnectTimeout(5000));

    }

    /****************************************************************************************************
     *      微信http服务
     * **************************************************************************************************
     */

    /**
     * 获取Access_token
     *      接口地址：https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
     * @param grant_type
     * @param appId
     * @param secret
     * @return
     */
    public Future<String> getAccessTokenByTime(String grant_type, String appId, String secret) {
        Future<String> accessFut = Future.future();
        StringBuilder uriBuffer = new StringBuilder();
        uriBuffer.append("/cgi-bin/token?grant_type=").append(grant_type)
                .append("&appid=").append(appId)
                .append("&secret=").append(secret);
        LOGGER.info("<<<before获取access_token->time=[{}],appId=[{}]：" , PeriodicTime.getCurrentTimeByyyyMMddHHmmss(), appId);
        wxClient.get(uriBuffer.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        accessFut.fail(resp.cause());
                    } else {
                        JsonObject jsonBody = resp.result().bodyAsJsonObject();
                        if (jsonBody.containsKey("access_token")) {
                            String accessToken = jsonBody.getString("access_token");
                            accessFut.complete(accessToken);
                        } else if (jsonBody.containsKey("errcode")) {
                            Integer errorCode = jsonBody.getInteger("errcode");
                            String errmsg = jsonBody.getString("errmsg");
                            LOGGER.info("获取access_token失败\n错误码[{" + errorCode + "}]\n错误信息[{" + errmsg + "}]");
                            accessFut.fail(errmsg);
                        } else {
                            accessFut.fail("[获取Token返回异常：] " + jsonBody);
                        }
                    }
                });
        return accessFut;
    }

    /**
     * 发送app回应消息至微信用户
     * @param replyJson
     * @param accessToken
     * @return
     */
    public Future<String> postReplyMsg2WxUser(JsonObject replyJson, String accessToken) {
        Objects.requireNonNull(replyJson);
        Objects.requireNonNull(accessToken);
        Future<String> sendMsgFut = Future.future();
        StringBuilder wxCustomerServiceBuffer = new StringBuilder()
                .append("/cgi-bin/message/custom/send?access_token=")
                .append(accessToken);
        LOGGER.info("[发送app回应消息至微信用户->token={}]", accessToken);
        wxClient.post(wxCustomerServiceBuffer.toString())
                .sendJsonObject(replyJson, resp -> {
                    if (resp.failed()) {
                        sendMsgFut.fail(resp.cause());
                    } else {
                        JsonObject retJson = resp.result().bodyAsJsonObject();
                        LOGGER.info("[发送app回应消息至微信用户->response={}]", retJson.encodePrettily());
                        if (retJson.getInteger("errcode") == 0 &&
                                "ok".equals(retJson.getString("errmsg"))) {
                            sendMsgFut.complete(resp.result().bodyAsString());
                        } else {
                            WxApiErrorResult errorResult = new WxApiErrorResult();
                            errorResult.setMsg(retJson.getString("errmsg"))
                                    .setCode(retJson.getInteger("errcode"));
                            sendMsgFut.fail(errorResult.toJson());
                        }
                    }
                });
        return sendMsgFut;
    }

    /**
     * 获取微信用户信息
     *      http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
     * @param openId
     * @return
     */
    public Future<WXUser> getWXUserInfoFromWX (String openId, String accessToken) {
        Future<WXUser> userFut = Future.future();
        StringBuilder uriBuffer = new StringBuilder();
        uriBuffer.append("/cgi-bin/user/info?access_token=").append(accessToken)
                .append("&openid=").append(openId)
                .append("&lang=").append("zh_CN");
        LOGGER.info("获取微信用户信息->openId={}, accessToken={}", openId, accessToken);
        wxClient.get(uriBuffer.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        userFut.fail(resp.cause());
                    } else {
                        JsonObject jsonBody = resp.result().bodyAsJsonObject();
                        if (jsonBody.containsKey("errcode")) {
                            Integer errorCode = jsonBody.getInteger("errcode");
                            String errmsg = jsonBody.getString("errmsg");
                            LOGGER.info("获取微信用户信息失败\n错误码[{" + errorCode + "}]\n错误信息[{" + errmsg + "}]");
                            userFut.fail(errorCode + "");
                        } else {
                            LOGGER.info("[获取微信用户信息->response={}]", jsonBody.encodePrettily());
                            WXUser user = new WXUser();
                            user.setWxOpenId(jsonBody.getString("openid"))
                                    .setHeadUrl(jsonBody.getString("headimgurl"))
                                    .setWxNickName(jsonBody.getString("nickname"))
                                    .setUnionid(jsonBody.getString("unionid"))
                                    .setSex(String.valueOf(jsonBody.getInteger("sex")))
                                    .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss());
                            userFut.complete(user);
                        }
                    }
                });
        return userFut;
    }

//    /**
//     * 创建公众号自定义菜单
//     *      http请求方式：POST（请使用https协议） https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
//     * @param accessToken
//     * @param menueJson
//     * @return
//     */
//    public Future<Void> createCustomizeMenu(String accessToken, JsonObject menueJson) {
//        JsonObject menuJsonObject;
//        RedirectUrl redirectUrl = new RedirectUrl();
//        redirectUrl.setRedirectUrl("http://www.quanzilife.cn/index.php?g=Wap&m=Index&a=content&id=807&classid=684&token=hzmndo1454489422")
//                .setState("ABC");
//        if (menueJson == null) {
//            menuJsonObject = new JsonObject();
//            JsonArray buttons = new JsonArray();
//            // 微官网
//            JsonObject microWebSite = new JsonObject();
//            JsonArray microWebSiteSubButtonArray = new JsonArray();
//            microWebSiteSubButtonArray
//                    .add(new JsonObject()
//                            .put("type", "click")
//                            .put("name", "最新消息")
//                            .put("key", "V1001_NEW_MESSAGE"))
//                    .add(new JsonObject()
//                            .put("type", "view")
//                            .put("name", "去官网")
//                            .put("url", redirectUrl.getRedirectUrl()));
//            microWebSite.put("name", "微官网")
//                    .put("sub_button", microWebSiteSubButtonArray);
//            // 产品中心
//            JsonObject productCenter = new JsonObject();
//            productCenter.put("type", "click")
//                    .put("name", "产品中心")
//                    .put("key", "V1001_PRODUCT_CENTER");
//
//            // 下载体验
//            JsonObject downloadExp = new JsonObject();
//            JsonArray downloadExpButtonArray = new JsonArray();
//            downloadExpButtonArray
//                    .add(new JsonObject()
//                            .put("name", "圈子APP")
//                            .put("type", "view")
//                            .put("url", "http://a.app.qq.com/o/simple.jsp?pkgname=org.cn.cnitr.electronic.quanchenmoments&g_f=991653#opened"))
//                    .add(new JsonObject()
//                            .put("name", "试用申请")
//                            .put("type", "view")
//                            .put("url", redirectUrl.setRedirectUrl("http://www.quanzilife.cn/index.php?g=Wap&m=Index&a=content&id=689&classid=685&token=hzmndo1454489422").getRedirectUrl()));
//            downloadExp.put("name", "下载体验")
//                    .put("sub_button", downloadExpButtonArray);
//
//            // 添加进菜单
//            buttons.add(microWebSite)
//                    .add(productCenter)
//                    .add(downloadExp);
//            menuJsonObject.put("button", buttons);
//
//        } else {
//            menuJsonObject = menueJson;
//        }
//        Future<Void> createMenuFut = Future.future();
//        StringBuilder uriBuffer = new StringBuilder();
//        uriBuffer.append("/cgi-bin/menu/create?access_token=").append(accessToken);
//        LOGGER.info("[创建公众号自定义菜单token={}]", accessToken);
//        wxClient.post(uriBuffer.toString())
//                .sendJsonObject(menuJsonObject, resp -> {
//                    if (resp.failed()) {
//                        createMenuFut.fail(resp.cause());
//                    } else {
//                        JsonObject retJson = resp.result().bodyAsJsonObject();
//                        LOGGER.info("[创建公众号自定义菜单->response={}]", retJson.encodePrettily());
//                        if (retJson.getInteger("errcode") == 0 &&
//                                "ok".equals(retJson.getString("errmsg"))) {
//                            createMenuFut.complete();
//                        } else {
//                            createMenuFut.fail("创建公众号自定义菜单失败：code[" + retJson.getInteger("errcode") + "]");
//                        }
//                    }
//                });
//        return createMenuFut;
//    }

//    /**
//     * 删除自定义菜单
//     *      https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
//     * @param accessToken
//     * @return
//     */
//    public Future<Void> deletePublicCustomizeMenu(String accessToken) {
//        Future<Void> delFut = Future.future();
//        StringBuilder uriSb = new StringBuilder("/cgi-bin")
//                .append("/menu/delete?access_token=").append(accessToken);
//        LOGGER.info("[删除自定义菜单token={}]", accessToken);
//        wxClient.get(uriSb.toString())
//                .send(resp -> {
//                    if (resp.failed()) {
//                        delFut.fail(resp.cause());
//                    } else {
//                        JsonObject retJson = resp.result().bodyAsJsonObject();
//                        LOGGER.info("[删除自定义菜单->response={}]", retJson.encodePrettily());
//                        if (retJson.getInteger("errcode") == 0 &&
//                                "ok".equals(retJson.getString("errmsg"))) {
//                            delFut.complete();
//                        } else {
//                            delFut.fail("删除自定义菜单失败：code[" + retJson.getInteger("errcode") + "]");
//                        }
//                    }
//                });
//        return delFut;
//    }

    // https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
    public Future<Void> uploadMPNewsImage(String accessToken, MPNews news) {
        Future<Void> uploadFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/cgi-bin/media/uploadimg")
                .append("?access_token=").append(accessToken);
        wxClient.post(uriSb.toString())
                .sendJsonObject(news.toJson(), resp -> {
                    if (resp.failed()) {
                        uploadFut.fail(resp.cause());
                    } else {
                        JsonObject retJson = resp.result().bodyAsJsonObject();
                        LOGGER.info("[微信上传素材->response={}]", retJson.encodePrettily());
                        if (retJson.getInteger("errcode") == 0 &&
                                "ok".equals(retJson.getString("errmsg"))) {
                            uploadFut.complete();
                        } else {
                            uploadFut.fail("微信上传素材失败：code[" + retJson.getInteger("errcode") + "]");
                        }
                    }
                });
        return uploadFut;
    }



    /**
     *  微信群发时上传图文素材
     *      https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=ACCESS_TOKEN
     * @param accessToken
     * @param news
     * @return
     */
    public Future<String> uploadMPNews(String accessToken, MPNews news) {
        Future<String> uploadFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/cgi-bin/media/uploadnews")
                .append("?access_token=").append(accessToken);
        wxClient.post(uriSb.toString())
                .sendJsonObject(news.toJson(), resp -> {
                    if (resp.failed()) {
                        uploadFut.fail(resp.cause());
                    } else {
                        JsonObject retJson = resp.result().bodyAsJsonObject();
                        LOGGER.info("[微信群发时上传图文素材->response={}]", retJson.encodePrettily());
                        if (retJson.getString("media_id") != null) {
                            uploadFut.complete(retJson.getString("media_id"));
                        } else {
                            uploadFut.fail("微信群发时上传图文素材失败：code[" + retJson.getInteger("errcode") + "]");
                        }
                    }
                });
        return uploadFut;
    }

    /**
     * 根据openID列表群发消息
     *      https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=ACCESS_TOKEN
     * @param accessToken
     * @param groupSend
     * @return
     */
    public Future<String> wxMsgGroupSendByList(String accessToken, WXGroupSendByList groupSend) {
        Future<String> groupSendFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/cgi-bin/message/mass/send")
                .append("?access_token=").append(accessToken);
        wxClient.post(uriSb.toString())
                .sendJsonObject(groupSend.toJson(), resp -> {
                    if (resp.failed()) {
                        groupSendFut.fail(resp.cause());
                    } else {
                        JsonObject retJson = resp.result().bodyAsJsonObject();
                        LOGGER.info("[根据openID列表群发消息->response={}]", retJson.encodePrettily());
                        if (retJson.getInteger("errcode") == 0) {
                            groupSendFut.complete(retJson.encodePrettily());
                        } else {
                            groupSendFut.fail("根据openID列表群发消息失败：code[" + retJson.getInteger("errcode") + "]");
                        }
                    }
                });
        return groupSendFut;
    }

    //    请注意，此处视频的media_id需通过POST请求到下述接口特别地得到：
    //    https://api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=ACCESS_TOKEN
    //    POST数据如下（此处media_id需通过基础支持中的上传下载多媒体文件来得到）：
    public Future<String> exchangeMediaId(String accessToken, String mediaId) {
        Future<String> exchangeFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/cgi-bin/media/uploadvideo")
                .append("?access_token=").append(accessToken);
        JsonObject mediaJson = new JsonObject();
        mediaJson.put("media_id", mediaId)
                .put("title", "标题")
                .put("description", "描述");
        wxClient.post(uriSb.toString())
                .sendJsonObject(mediaJson, resp -> {
                    if (resp.failed()) {
                        exchangeFut.fail(resp.cause());
                    } else {
                        JsonObject retJson = resp.result().bodyAsJsonObject();
                        LOGGER.info("[转换mediaId->response={}]", retJson.encodePrettily());
                        if (StringUtils.isNotEmpty(retJson.getString("media_id"))) {
                            exchangeFut.complete(retJson.getString("media_id"));
                        } else {
                            exchangeFut.fail("转换mediaId失败：code[" + retJson.getInteger("errcode") + "]");
                        }
                    }
                });
        return exchangeFut;
    }

    /****************************************************************************************************
     *      微博http服务
     * **************************************************************************************************
     */

    /**
     * 获取微博用户信息
     *  https://api.weibo.com/2/eps/user/info.json
     * @param uid
     * @param accessToken
     * @return
     */
    public Future<WXUser> getWBUserByUID (String uid, String accessToken) {
        Future<WXUser> userFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/2/eps/user/info.json")
                .append("?uid=").append(uid)
                .append("&access_token=").append(accessToken);
        wbClient.get(uriSb.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        userFut.fail(resp.cause());
                    } else {
                        JsonObject jsonBody = resp.result().bodyAsJsonObject();
                        if (jsonBody.containsKey("error_code")) {
                            Integer errorCode = jsonBody.getInteger("error_code");
                            String errmsg = jsonBody.getString("error");
                            LOGGER.info("获取微博用户信息失败\n错误码[{" + errorCode + "}]\n错误信息[{" + errmsg + "}]");
                            if (30020 == jsonBody.getInteger("error_code")) {
                                // 用户不存在
                                userFut.complete(null);
                            }
                            userFut.fail(errorCode + "");
                        } else {
                            LOGGER.info("[获取微博用户信息->response={}]", jsonBody.encodePrettily());
                            WXUser user = new WXUser();
                            user.setWxOpenId(String.valueOf(jsonBody.getLong("id")))
                                    .setHeadUrl(jsonBody.getString("headimgurl_hd"))
                                    .setWxNickName(jsonBody.getString("nickname"))
                                    .setUnionid(jsonBody.getString("unionid"))
                                    .setSex(String.valueOf(jsonBody.getInteger("sex")))
                                    .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss());
                            userFut.complete(user);
                        }
                    }
                });
        return userFut;
    }

    /**
     * 回复微博用户
     *      短连接 https://m.api.weibo.com/2/messages/reply.json
     *      长连接 https://m.api.weibo.com/2/messages/reply.json
     *
     * @param accessToken
     * @param msg
     * @return
     */
    public Future<Void> sendMsgToFans (String accessToken, WBTextMsg msg) {
        Future<Void> sendFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/2/messages/reply.json")
                .append("?access_token=").append(accessToken);
        wbClient.post("m.api.weibo.com", uriSb.toString())
                .putHeader("Content-Type", "application/x-www-form-urlencoded")
                .sendBuffer(Buffer.buffer(msg.toMsgStr()), resp -> {
                    if (resp.failed()) {
                        sendFut.fail(resp.cause());
                    } else {
                        JsonObject jsonBody = resp.result().bodyAsJsonObject();
                        if (jsonBody.containsKey("result")) {
                            LOGGER.info("[回复微博用户->response={}]", jsonBody.encodePrettily());
                            sendFut.complete();
                        } else {
                            Integer errorCode = jsonBody.getInteger("error_code");
                            String errmsg = jsonBody.getString("error");
                            LOGGER.info("回复微博用户失败\n错误码[{" + errorCode + "}]\n错误信息[{" + errmsg + "}]");
                            sendFut.fail(errorCode + "");
                        }
                    }
                });
        return sendFut;
    }


    /**
     * 修改微博url回调格式
     *      https://api.weibo.com/2/eps/push/set_format.json
     * @param accessToken
     * @param format
     * @return
     */
    public Future<Void> changeCallBackStyle (String accessToken, String format) {
        Future<Void> changeCallBackWayFut = Future.future();
        StringBuilder uriSb = new StringBuilder("/2/eps/push/set_format.json")
                .append("?access_token=").append(accessToken)
                .append("&format=").append(format);
        wbClient.post(uriSb.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        changeCallBackWayFut.fail(resp.cause());
                    } else {
                        JsonObject jsonBody = resp.result().bodyAsJsonObject();
                        if (jsonBody.containsKey("error")) {
                            Integer errorCode = jsonBody.getInteger("error_code");
                            String errmsg = jsonBody.getString("error");
                            LOGGER.info("modifiy weibo callback style failed\nerrcode[{" + errorCode + "}]\nerror[{" + errmsg + "}]");
                            changeCallBackWayFut.fail(errorCode + "");
                        } else {
                            LOGGER.info("[modifiy weibo callback style ->response={}]", jsonBody.encodePrettily());
                            changeCallBackWayFut.complete();
                        }
                    }
                });
        return changeCallBackWayFut;
    }

    /**
     * 获取微博公众平台下粉丝列表（一次）
     *      https://m.api.weibo.com/2/messages/subscribers/get.json
     *
     *      access_token	true	string	在粉丝服务平台 - 高级功能 - 开发者模式页面中获取，或者OAuth2.0授权后获得, 详细参考 获取粉丝服务平台开发接口的access token。
     *      next_uid	    false	int64	第一个拉取的uid，不填默认从头开始拉取。
     * @param token
     * @param nextUid
     * @return
     */
    public Future<WBFansList> getFansUidListByToken(String token, String nextUid) {
        Future<WBFansList> uiddListFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append("/2/messages/subscribers/get.json?access_token=").append(token);
        if (StringUtils.isNotBlank(nextUid)) {
            uriSb.append("&next_uid=").append(nextUid);
        }
        get(wbmClient, uriSb, null)
                .compose(uidJson -> {
                    if (uidJson.containsKey("error_code")) {
                        uiddListFut.fail(uidJson.getString("error_code"));
                    } else {
                        WBFansList fansList = uidJson.mapTo(WBFansList.class);
                        if (fansList == null) {
                            uiddListFut.fail("fansList is empty");
                        } else {
                            uiddListFut.complete(fansList);
                        }
                    }
                }, uiddListFut);
        return uiddListFut;
    }

    /**
     * 循环遍历微博用户列表中所有粉丝
     * @param token
     * @param nextUid
     * @param uidListFuture
     * @param uidList
     */
    public void getUIdListByLoopFromWB(String token, String nextUid, Future<List<String>> uidListFuture, List<String> uidList) {
        getFansUidListByToken(token, nextUid)
                .compose(fansList -> {
                    uidList.addAll(fansList.getUidList());
                    if (fansList.getTotal() > fansList.getCount()) {
                        getOpenIdListByLoop(token, fansList.getNext_uid(), uidListFuture, uidList);
                    } else {
                        uidListFuture.complete(uidList);
                    }
                }, uidListFuture);
    }

    /**
     * 根据access_token获取微博平台receiverId(uid)
     * @param accessToken
     * @return
     */
    public Future<String> getUidFromAccessToken(String accessToken) {
        return Future.future(fut -> {
            StringBuilder uriSb = new StringBuilder("/oauth2/get_token_info?")
                    .append("access_token=").append(accessToken);
            post(wbClient, uriSb, null)
                    .compose(resp -> {
                        if(resp.containsKey("error_code")) {
                            fut.fail(resp.getString("error"));
                        } else {
                            fut.complete(String.valueOf(resp.getLong("uid")));
                        }
                    }, fut);
        });
    }

    /****************************************************************************************************
     *      融云http服务
     * **************************************************************************************************
     */

    /**
     * 设置融云请求头信息
     * @param request
     * @return
     */
    public HttpClientRequest setRCRequestHeaders(HttpClientRequest request) {
        // 拼接请求头
        String nonce = String.valueOf(Math.random() * 1000000);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        String signature = RCUtils.getRongCloudSign(HttpService.RC_ISTEST ? Constants.RC_APP_SECRET_TEST : Constants.RC_APP_SECRET,
                nonce, timestamp);
        request
                .putHeader("App-Key", HttpService.RC_ISTEST ? Constants.RC_APP_APPKEY_TEST : Constants.RC_APP_APPKEY)
                .putHeader("Nonce", nonce)
                .putHeader("Timestamp", timestamp)
                .putHeader("Signature", signature)

                .putHeader("content-type", "application/x-www-form-urlencoded")
                .setTimeout(Constants.REQUEST_TIMEOUT);
        return request;
    }

    /****************************************************************************************************
     *      app后台http服务
     * **************************************************************************************************
     */
    /**
     * 获取所有公众号信息
     *      数据格式：json
     *      {
     "publics":[
     {
     "appId":"wx1315b6f5c2a9ed7f",								appId：公众号唯一标识
     "secret":"9b9d7b201fda68ab957c2427516d00a7",				secret：公众号通信加密秘钥
     "developerId":"gh_0ddb7b0febec",							developerId：开发者微信号（公众号原始ID）
     "name":"圈子生活o2o"										name: 公众号名称
     },
     {
     "appId":"wx9895c4baa57c9e33",
     "secret":"f80a24ec88841c75fd6c7fad436201d9",
     "developerId":"gh_3e93bed64082",
     "name":"壮我大"
     }
     ]
     }
     * @return
     */
    public Future<List<WXPublic>> getPublicListFromAppBackground() {
        Future pubListFut = Future.future();
        List<WXPublic> publicList = new ArrayList<>();
        LOGGER.info("获取所有公众号信息->");
        appClient.get(Constants.APPBACK_INTERFACE_URI_ALL_PUBLIC)
                .send(resp -> {
                    if (resp.failed()) {
                        pubListFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(pubListFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[获取所有公众号信息->response={}]", result.encodePrettily());
                        JsonArray pubJsonArray = result.getJsonArray("publics");
                        for (int i = 0; i < pubJsonArray.size(); i++) {
                            JsonObject pubJson = pubJsonArray.getJsonObject(i);
                            WXPublic pub = pubJson.mapTo(WXPublic.class);
                            publicList.add(pub);
                        }
                        pubListFut.complete(publicList);
                    }
                });
        return pubListFut;
    }

    /**
     *  根据openid获取对应接应的客服userid
     *      数据格式：json
     *          {
     *              "csId", "XXX",
     *              "userId", "XXX"
     *          }
     * @return
     */
    public Future<AppUser> getCustomerServerByOpenidFromAppBack(String openId, String developerId, Integer channelType) {
        Future<AppUser> csFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_CS + "?developerId=").append(developerId)
                .append("&channelType=").append(channelType);
        LOGGER.info("根据openid获取对应接应的客服->url={}", uriSb.toString());
        appClient.get(uriSb.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        csFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(csFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[根据openid获取对应接应的客服userid->response={}]", result.encodePrettily());
                        if (result.isEmpty()) {
                            csFut.fail("[根据openid获取对应接应的客服userid]请求返回空为{}");
                        } else {
                            AppUser user = new AppUser();
                            user.setOpenId(openId)
                                    .setNickname(result.getString("nickname"))
                                    .setCsId(String.valueOf(result.getInteger("csId")))
                                    .setTalkId(result.getString("userId"))
                                    .setStatus("0")
                                    .setPullBlack(0)
                                    .setCreateTime(PeriodicTime.getCurrentTimeByyyyMMddHHmmss());
                            csFut.complete(user);
                        }
                    }
                });
        return csFut;
    }
    
   /**
    * app后台获取客服问候语
    * @param developerId
    * @param channelType
    * @return
    */
    public Future<String> getCustomerGreetingFromAppBack(String developerId, Integer channelType) {
        Future<String> wenHouFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_CS + "?developerId=").append(developerId)
                .append("&channelType=").append(channelType);
        LOGGER.info("app后台获取客服问候语->developerId={}", developerId);
        appClient.get(uriSb.toString())
                .send(resp -> {
                    if (resp.failed()) {
                    	wenHouFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(wenHouFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[app后台获取客服问候语->response={}]", result.encodePrettily());
                        if (result.isEmpty()) {
                        	wenHouFut.fail("[app后台获取客服问候语]请求返回空为{}");
                        } 
                        else {
                        	String greeting = result.getString("greeting");
                        	wenHouFut.complete(greeting);
                        }
                    }
                });
        return wenHouFut;
    }

    /**
     * 从app后台获取指定客服问候语
     * @param csId
     * @return
     */
    public Future<String> getCSGreetingByCsId(Integer csId) {
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_CS_GREETING)
                .append("?csId=").append(csId);
        LOGGER.info("从app后台获取指定客服问候语->csId={}", csId);
        return Future.future(greetingFut -> {
            get(appClient, uriSb, null)
                    .compose(respJson -> {
                        LOGGER.info("[从app后台获取指定客服问候语->response={}]", respJson.encodePrettily());
                        if (1 == respJson.getInteger("type")) {
                            greetingFut.complete(respJson.getString("greeting"));
                        } else {
                            greetingFut.fail("从app后台获取指定客服问候语请求返回异常：" + respJson);
                        }
                    }, greetingFut);
        });
    }

    /**
     * 发送新用户信息给app后台
     *      数据格式：json
     *      {
     *          "openId": "",
     *          "nickName": "",
     *          "headUrl": "",
     *          "customerServer": ""
     *      }
     * @param newUser
     * @param csId
     * @return
     */
    public Future<Void> sendNewWXUserToAppBack(WXUser newUser, String csId, Integer channelType) {
        JsonObject newUserJson = new JsonObject();
        newUserJson.put("openId", newUser.getWxOpenId())
                .put("channelType", channelType)
                .put("nickName", newUser.getWxNickName())
                .put("headUrl", newUser.getHeadUrl())
                .put("customerServer", csId);
        Future<Void> userFut = Future.future();
        LOGGER.info("发送新用户信息给app后台start");
        appClient.post(Constants.APPBACK_INTERFACE_URI_NEW_APPUSER)
                .sendJsonObject(newUserJson, resp -> {
                    if (resp.failed()) {
                        userFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(userFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[发送新用户信息给app后台->response={}]", result.encodePrettily());
                        userFut.complete();
                    }
                });
        return userFut;
    }

    /**
     * 上传聊天历史记录
     * @param records
     * @return
     */
    public Future<Void> sendHistoryMsgRecord(List<MsgRecord> records) {
        Future<Void> sendFut = Future.future();
        if (records.size() > 0) {
            JsonArray recordArray = new JsonArray();
            for(MsgRecord record : records) {
                // 消息内容emoji解码
                record.decodeContent();
                JsonObject r = JsonObject.mapFrom(record);
                r.remove("id");
                r.remove("isRead");
                r.put("content", record.contentByType());
                recordArray.add(r);
            }
            JsonObject returnJson = new JsonObject();
            returnJson.put("records", recordArray);
            appClient.post(Constants.APPBACK_INTERFACE_URI_HISTORY_MSGRECORD)
                    .sendJsonObject(returnJson, resp -> {
                        if (resp.failed()) {
                            sendFut.fail(resp.cause());
                        } else {
                            // 检查返回
                            checkResponse(sendFut, resp.result());

                            JsonObject result = resp.result().bodyAsJsonObject();
                            LOGGER.info("[上传聊天历史记录->response={}]", result.encodePrettily());
                            sendFut.complete();
                        }
                    });
        } else {
            sendFut.complete();
        }
        return sendFut;
    }

    /**
     * 发送请求，通知app后台，用户取消关注
     * @param openId
     * @return
     */
    public Future<Void> userUnsubcribe(String openId) {
        Future<Void> unsubFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_UNSUBCRIBE).append("?openid=").append(openId);
        LOGGER.info("根据openid通知app后台用户取消关注->openid={}", openId);

        get(appClient, uriSb, null)
                .compose(respJson -> {
                    LOGGER.info("[用户取消关注->response={}]", respJson.encodePrettily());
                    if (1 == respJson.getInteger("result")) {
                        unsubFut.complete();
                    } else {
                        unsubFut.fail("根据openid通知app后台用户取消关注请求返回异常：" + respJson);
                    }
                }, unsubFut);
        return unsubFut;
    }

    /**
     * 请求微官网信息
     * @param developerId
     * @param csId
     * @return
     */
    public Future<JsonObject> getMicroWebSiteInfo(String developerId, String csId) {
        Future<JsonObject> microwebFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_MICROWEBSITE)
                .append("?developerId=").append(developerId)
                .append("&csId=").append(csId);
        LOGGER.info("[请求微官网信息start]");
        appClient.get(uriSb.toString())
                .send(resp -> {
                    if (resp.failed()) {
                        microwebFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(microwebFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[请求微官网信息->response={}]", result.encodePrettily());
                        if (result.isEmpty()) {
                            microwebFut.complete(null);
                        } else {
                            microwebFut.complete(result);
                        }
                    }
                });
        return microwebFut;
    }

    /**
     * 发送用户轨迹信息给app 后台
     * @param userTrack
     * @return
     */
    public Future<JsonObject> postUserTrackToAppBack(JsonObject userTrack) {
        Future<JsonObject> postUserTrackFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append(Constants.APPBACK_INTERFACE_URI_USER_TRACK);
        appClient.post(uriSb.toString())
                .sendJsonObject(userTrack, resp -> {
                    if (resp.failed()) {
                        postUserTrackFut.fail(resp.cause());
                    } else {
                        // 检查返回
                        checkResponse(postUserTrackFut, resp.result());

                        JsonObject result = resp.result().bodyAsJsonObject();
                        LOGGER.info("[发送用户轨迹信息给app 后台->response={}]", result.encodePrettily());
                        if (1 == result.getInteger("result")) {
                            postUserTrackFut.complete();
                        } else {
                            postUserTrackFut.fail("[发送用户轨迹信息给app后台]请求返回异常：" + result);
                        }
                    }
                });
        return postUserTrackFut;
    }

    /**
     * 获取公众号下的粉丝列表（一次）
     *   https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
     * @param token
     * @param nextOpenId
     * @return
     */
    public Future<FansList> getFansOpenIdListByToken(String token, String nextOpenId) {
        Future<FansList> openIdListFut = Future.future();
        StringBuilder uriSb = new StringBuilder();
        uriSb.append("/cgi-bin/user/get?access_token=").append(token);
        if (StringUtils.isNotBlank(nextOpenId)) {
            uriSb.append("&next_openid=").append(nextOpenId);
        }
        get(wxClient, uriSb, null)
                .compose(openIdJson -> {
                    if (openIdJson.containsKey("errcode")) {
                        openIdListFut.fail(openIdJson.getString("errmsg"));
                    } else {
                        FansList fansList = openIdJson.mapTo(FansList.class);
                        if (fansList == null) {
                            openIdListFut.fail("fansList is empty");
                        } else {
                            openIdListFut.complete(fansList);
                        }
                    }
                }, openIdListFut);
        return openIdListFut;
    }

    /**
     * 循环遍历微信用户列表中所有粉丝
     * @param token
     * @param nextOpenId
     * @param openIdListFuture
     * @param openIdList
     */
    public void getOpenIdListByLoop(String token, String nextOpenId, Future<List<String>> openIdListFuture, List<String> openIdList) {
        getFansOpenIdListByToken(token, nextOpenId)
                .compose(fansList -> {
                    openIdList.addAll(fansList.getOpenIdList());
                    if (fansList.getTotal() > fansList.getCount()) {
                        getOpenIdListByLoop(token, fansList.getNext_openid(), openIdListFuture, openIdList);
                    } else {
                        openIdListFuture.complete(openIdList);
                    }
                }, openIdListFuture);
    }

    /**
     * 拉黑一个列表的粉丝
     *      http请求方式：POST（请使用https协议）
     *      https://api.weixin.qq.com/cgi-bin/tags/members/batchblacklist?access_token=ACCESS_TOKEN
     * @param token
     * @param openIdList
     * @return
     */
    public Future<Void> pullBlackByList(String token, List<String> openIdList) {
        return Future.future(pullBackFut -> {
            StringBuilder pullBackUri = new StringBuilder();
            pullBackUri.append("/cgi-bin/tags/members/batchblacklist?access_token=").append(token);
            JsonObject openidJson = new JsonObject()
                    .put("openid_list", openIdList);
            post(wxClient, pullBackUri, openidJson)
                    .compose(respJson -> {
                        String errmsg = respJson.getString("errmsg");
                        Integer errcode = respJson.getInteger("errcode");
                        if("ok".equals(errmsg) && 0 == errcode) {
                            LOGGER.debug("拉黑粉丝成功");
                            pullBackFut.complete();
                        } else {
                            LOGGER.debug("粉丝拉黑失败：errmsg={}， errcode={} \nopenIdList={}", errmsg, errcode, Json.encodePrettily(openIdList));
                            pullBackFut.fail("拉黑粉丝失败：errcode=" + errcode + "\nopenidList=" + Json.encodePrettily(openIdList));
                        }
                    }, pullBackFut);
        });
    }

    /**
     *  取消拉黑粉丝
     *      http请求方式：POST（请使用https协议）
     *      https://api.weixin.qq.com/cgi-bin/tags/members/batchunblacklist?access_token=ACCESS_TOKEN
     * @param token
     * @param openIdList
     * @return
     */
    public Future<Void> canclePullBackByList(String token, List<String> openIdList) {
        return Future.future(canclePullBackFut -> {
            StringBuilder pullBackUri = new StringBuilder();
            pullBackUri.append("/cgi-bin/tags/members/batchunblacklist?access_token=").append(token);
            JsonObject openidJson = new JsonObject()
                    .put("openid_list", openIdList);
            post(wxClient, pullBackUri, openidJson)
                    .compose(respJson -> {
                        String errmsg = respJson.getString("errmsg");
                        Integer errcode = respJson.getInteger("errcode");
                        if("ok".equals(errmsg) && 0 == errcode) {
                            LOGGER.debug("取消拉黑粉丝成功");
                            canclePullBackFut.complete();
                        } else {
                            LOGGER.debug("取消粉丝拉黑失败：errmsg={}， errcode={} \nopenIdList={}", errmsg, errcode, Json.encodePrettily(openIdList));
                            canclePullBackFut.fail("取消拉黑粉丝失败：errcode=" + errcode + "\nopenidList=" + Json.encodePrettily(openIdList));
                        }
                    }, canclePullBackFut);
        });
    }

    //--------------------------------------------------------------------------
    //  通用请求类型
    //--------------------------------------------------------------------------
    public Future<JsonObject> get(WebClient client, StringBuilder uri, JsonObject sendJson) {
        Future<JsonObject> respJsonFut = Future.future();
        if (sendJson == null) {
            client.get(uri.toString())
                    .send(getResp -> {
                        dealCommonResp(getResp, respJsonFut, uri);
                    });
        } else {
            client.get(uri.toString())
                    .sendJsonObject(sendJson, getResp -> {
                        dealCommonResp(getResp, respJsonFut, uri);
                    });
        }
        return respJsonFut;
    }

    public Future<JsonObject> post(WebClient client, StringBuilder uri, JsonObject sendJson) {
        Future<JsonObject> respJsonFut = Future.future();
        if (sendJson == null) {
            client.post(uri.toString())
                    .send(getResp -> {
                        dealCommonResp(getResp, respJsonFut, uri);
                    });
        } else {
            client.post(uri.toString())
                    .sendJsonObject(sendJson, getResp -> {
                       dealCommonResp(getResp, respJsonFut, uri);
                    });
        }
        return respJsonFut;
    }

    /**
     * 通用处理请求返回
     * @param getResp
     * @param respJsonFut
     */
    public void dealCommonResp(AsyncResult<HttpResponse<Buffer>> getResp, Future respJsonFut, StringBuilder uri) {
        if (getResp.failed()) {
            respJsonFut.fail(getResp.cause());
        } else {
            // 检查返回
            checkResponse(respJsonFut, getResp.result());
            JsonObject result = getResp.result().bodyAsJsonObject();
            LOGGER.info("[{}]response->\n{}", uri.toString(), result.encodePrettily());
            respJsonFut.complete(result);
        }
    }

    public String getAPPBackgroundHost () {
        if (HttpService.APPBACK_ISTEST) {
            return Constants.APPBACK_INTERFACE_HOST_TEST;
        } else {
            return Constants.APPBACK_INTERFACE_HOST;
        }
    }

    public Integer getAPPBackgroundPort() {
        if (HttpService.APPBACK_ISTEST) {
            return Constants.APPBACK_INTERFACE_PORT_TEST;
        } else {
            return Constants.APPBACK_INTERFACE_PORT;
        }
    }

    /**
     * 检查请求返回是否异常
     * @param fut
     * @param response
     */
    public void checkResponse(Future fut, HttpResponse response) {
        if (response == null) {
            fut.fail(new RequestAppBackException("请求App后台返回异常[response == null]"));
        } else if (response.statusCode() != 200) {
            fut.fail(new RequestAppBackException("请求App后台返回异常[code=" + response.statusCode() + ";msg=]" + response.statusMessage()));
        }
    }

}
